﻿using System;
using System.Linq;
using OpenTK.Graphics.OpenGL4;
using OpenTK;

namespace Painter3D.RenderSys.RenderObject
{
    public abstract class VertexData : IDisposable
    {
        /// <summary>
        /// 顶点数据包含的顶点信息
        /// </summary>
        protected ShaderInfo.VertexRequestTagEnum[] enableReqs;
        /// <summary>
        /// VAO句柄
        /// </summary>
        readonly protected int VAO;
        /// <summary>
        /// VBO句柄
        /// 顶点数据按照顶点顺序依次存储
        /// </summary>
        readonly protected int VBO;
        /// <summary>
        /// 生成的数据是否可以被动态修改
        /// </summary>
        readonly protected BufferUsageHint hint;
        /// <summary>
        /// 图形的包围盒
        /// </summary>
        public readonly BoundBox boundBox;

        protected VertexData(float[] position)
        {
            boundBox = new BoundBox(position);
            enableReqs = new ShaderInfo.VertexRequestTagEnum[] { ShaderInfo.VertexRequestTagEnum.Position, };
            VAO = GL.GenVertexArray();
            VBO = GL.GenBuffer();
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            GL.BufferData(BufferTarget.ArrayBuffer, position.Length * sizeof(float), position, BufferUsageHint.StaticDraw);
        }

        protected VertexData(float[] position, float[] UV)
        {
            boundBox = new BoundBox(position);
            enableReqs = new ShaderInfo.VertexRequestTagEnum[] { ShaderInfo.VertexRequestTagEnum.Position, ShaderInfo.VertexRequestTagEnum.UV1 };
            int pointCount = position.Length / 3;
            float[] data = new float[pointCount * StrideInFloat()];
            for (int i = 0; i < pointCount; i++)
            {
                data[i * 5] = position[i * 3];
                data[i * 5 + 1] = position[i * 3 + 1];
                data[i * 5 + 2] = position[i * 3 + 2];
                data[i * 5 + 3] = UV[i * 2];
                data[i * 5 + 4] = UV[i * 2 + 1];
            }
            VAO = GL.GenVertexArray();
            VBO = GL.GenBuffer();
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            GL.BufferData(BufferTarget.ArrayBuffer, pointCount * StrideInByte(), data, BufferUsageHint.StaticDraw);
        }

        protected VertexData(ShaderInfo.VertexRequestTagEnum[] requestTags, float[][] datas, BufferUsageHint hint = BufferUsageHint.StaticDraw)
        {
            boundBox = new BoundBox(datas[0]);
            this.hint = hint;
            enableReqs = requestTags;
            if (enableReqs == null)
            {
                throw new Exception("顶点数组对象尚未申明数据请求类型");
            }
            //基于position组件计算顶点数量.默认第一个数组为位置信息
            int PointCount = datas[0].Length / 3;
            //检查各个数据数量是否充足
            for (int i = 1; i < datas.Length; i++)
            {
                if (datas[i].Length < PointCount * enableReqs[i].TagCount())
                {
                    throw new Exception("构建顶点数据时，数据项长度不足。问题项为:" + enableReqs[i].ToString());
                }
            }
            int stride = StrideInFloat();
            float[] data = new float[PointCount * stride];
            for (int i = 0; i < PointCount; i++)
            {
                int count = 0;
                for (int j = 0; j < enableReqs.Length; j++)
                {
                    var req = enableReqs[j];
                    for (int k = 0; k < req.TagCount(); k++)
                    {
                        data[i * stride + count + k] = datas[j][i * req.TagCount() + k];
                    }
                    count += req.TagCount();
                }
            }
            VAO = GL.GenVertexArray();
            VBO = GL.GenBuffer();
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            GL.BufferData(BufferTarget.ArrayBuffer, PointCount * StrideInByte(), data, hint);
        }

        protected VertexData(ShaderInfo.VertexRequestTagEnum[] requestTags, float[] data, BufferUsageHint hint = BufferUsageHint.StaticDraw)
        {
            this.hint = hint;
            enableReqs = requestTags;
            if (enableReqs == null)
            {
                throw new Exception("顶点数组对象尚未申明数据请求类型");
            }

            boundBox = new BoundBox(ExtractByTag(ShaderInfo.VertexRequestTagEnum.Position, data));

            int stride = StrideInFloat();
            VAO = GL.GenVertexArray();
            VBO = GL.GenBuffer();
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            GL.BufferData(BufferTarget.ArrayBuffer, data.Length * sizeof(float), data, hint);
        }

        /// <summary>
        /// 更新顶点对象的数据，必须在声明时作为dynamic/stream对象
        /// </summary>
        /// <param name="data">重新载入的数据</param>
        public virtual void Reload(float[] data)
        {
            if (hint == BufferUsageHint.StaticDraw)
            {
                throw new Exception("无法修改静态对象的数据");
            }
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr)0, data.Length * sizeof(float), data);
        }

        /// <summary>
        /// 从混合好的数据项中提取出需要的顶点请求
        /// </summary>
        /// <param name="tag">请求数据</param>
        /// <param name="data">混合布局的数据</param>
        /// <returns>提取出的数据</returns>
        protected float[] ExtractByTag(ShaderInfo.VertexRequestTagEnum tag, float[] data)
        {
            int stride = StrideInFloat();
            int count = tag.TagCount();
            int offset = 0;
            foreach (var beflorTag in enableReqs)
            {
                if (beflorTag == tag)
                {
                    break;
                }
                offset += beflorTag.TagCount();
            }
            float[] exData = new float[data.Length / stride * count];
            for (int i = 0; i < data.Length / stride; i++)
            {
                for (int k = 0; k < count; k++)
                {
                    exData[i * count + k] = data[i * stride + offset + k];
                }
            }
            return exData;
        }

        static public float[] EmptyBuffer(int size)
        {
            return new float[size];
        }


        /// <summary>
        /// 判断顶点是否有对应的数据类型支持
        /// </summary>
        /// <param name="tag">检查是否包含的数据类型</param>
        /// <returns>true 包含该类信息</returns>
        private bool HasTagSupport(ShaderInfo.VertexRequestTagEnum tag)
        {
            return enableReqs.Contains(tag);
        }
        /// <summary>
        /// 计算当前一组顶点数据的字节长度
        /// </summary>
        /// <returns>一组顶点数据的字节长度</returns>
        protected int StrideInByte()
        {
            int stride = 0;
            foreach (var req in enableReqs)
            {
                stride += req.TagSize();
            }
            return stride;
        }
        /// <summary>
        /// 计算当前一组顶点数据的float个数
        /// </summary>
        /// <returns>float个数</returns>
        protected int StrideInFloat()
        {
            int stride = 0;
            foreach (var req in enableReqs)
            {
                stride += req.TagCount();
            }
            return stride;
        }
        /// <summary>
        /// 计算当前请求标志位的字节偏移
        /// </summary>
        /// <param name="tag">当前请求标志位</param>
        /// <returns>字节偏移</returns>
        protected int Offset(ShaderInfo.VertexRequestTagEnum tag)
        {
            int offset = 0;
            foreach (var req in enableReqs)
            {
                if (req != tag)
                {
                    offset += req.TagSize();
                }
                else
                {
                    return offset;
                }
            }
            return -1;
        }
        /// <summary>
        /// 完成数据绑定的设置
        /// 按照请求完成顶点数据的绑定
        /// </summary>
        /// <param name="infos">绘制需要的定点信息</param>
        public void BindData(ShaderInfo.VertexRequestInfo[] infos)
        {
            GL.BindVertexArray(VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
            foreach (var reqInfo in infos)
            {
                if (!HasTagSupport(reqInfo.tag))
                {
                    throw new Exception("顶点数组对象不包含请求的顶点信息" + reqInfo.tag.ToString());
                }
                GL.VertexAttribPointer(reqInfo.point, reqInfo.tag.TagCount(), VertexAttribPointerType.Float, false, StrideInByte(), Offset(reqInfo.tag));
                GL.EnableVertexAttribArray(reqInfo.point);
            }
        }
        /// <summary>
        /// 绘制当前数据
        /// </summary>
        public abstract void DrawData();

        #region Dispose

        protected virtual void Dispose(bool manule)
        {
            if (manule)
            {
                GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
                GL.DeleteBuffer(VBO);
                GL.DeleteVertexArray(VAO);
            }
            else
                throw new Exception(String.Format("GL资源泄露{0}", this));
        }

        /// <summary>
        /// 释放对象在显存申请的空间
        /// </summary>
        /// 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~VertexData()
        {
            Dispose(false);
        }
        #endregion
    }
}
